﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Effects.Effects.GuiParts;
using Effects.Effects.Tools;

namespace Effects.Effects
{
    /// <summary>
    ///  Vibrato implementation.
    /// </summary>
    /// <remarks>
    ///   <para>
    ///     Simulates player using the vibrato technique while playing the note.
    ///   </para>
    ///   <para>
    ///     Use similar approach as <see cref="Flanger"/> but has differently tuned parameters and feedback parameter is missing.
    ///   </para>
    /// </remarks>
    public class Vibrato : Effect
    {
        #region consts

        /// <summary>
        ///  Const storing delay time of the vibrato.
        /// </summary>
        protected const double vibratoDelayTime = 30;

        #endregion

        #region controls
        // fields and properties for user controlled parameters 

        #region footswitch

        /// <summary>
        ///  Footswitch for by passing effect.
        /// </summary>
        private Footswitch footswitch;

        /// <summary>
        ///  Led indicating On/Off state.
        /// </summary>
        private LED led;

        /// <summary>
        ///  Synchornizer for footswitch and led.
        /// </summary>
        private FootswitchLedSynchronizer synchronizer;


        /// <summary>
        ///  Key for footswitch settings.
        /// </summary>
        private int footswitchKey = 0;

        #endregion

        #region speed
        
        /// <summary>
        ///  Potenciometer controlling speed - period of oscillation.
        /// </summary>
        protected Pot Speed;

        /// <summary>
        ///  Current speed parameter value.
        /// </summary>
        private int speed;
        
        /// <summary>
        ///  Key for the speed parameter
        /// </summary>
        private const int speedKey = 1;

        #endregion

        #region depth

        /// <summary>
        ///  Potentiometer controlling the depth parameter
        /// </summary>
        protected Pot Depth;

        /// <summary>
        ///  Current depth parameter value. (dry/wet mix)
        /// </summary>
        private double depth;
        
        /// <summary>
        ///  Key for the depth parameter.
        /// </summary>
        private int depthKey = 2;
        #endregion

        #region oscillationType
        /// <summary>
        ///  Switches among different oscillation types.
        /// </summary>
        protected Switch oscilatorSwitch;

        /// <summary>
        ///  Current type of the oscillation. 
        /// </summary>
        protected int oscillationType;

        /// <summary>
        ///  Key for oscillation type parameter.
        /// </summary>
        private const int oscilatorTypeKey = 3;

        #endregion

        #endregion

        #region algorithm_fields
        // necessary fields for algorithm

        /// <summary>
        ///   Time interval between 2 adjecant samples given to be processed  
        /// </summary>
        private double step;

        /// <summary>
        ///  Delay line to delay the signal.
        /// </summary>
        private OscillatingDelayLine buffer;

        #endregion

        #region effects_properties
        // overridden preperties of base class Effect

        /// <summary>
        ///  Gets or sets the sample rate.
        /// </summary>
        /// <remarks>
        ///  Change of sample rate causes also change of step and length of buffer.
        /// </remarks>
        public override uint SampleRate
        {
            set
            {
                sampleRate = value;
                step = 1000 / (double)sampleRate;
                //buffer = new TimeVaryingBuffer(Convert.ToInt32(Math.Ceiling((double)5 / step)), buffer.Oscilator);
                buffer = new OscillatingDelayLine(vibratoDelayTime, (int) sampleRate, buffer.Oscillator, 0);
                buffer.Oscillator.Period = speed;
            }
        }

        /// <summary>
        ///  Gets tha name of the effect.
        /// </summary>
        public override string Name
        {
            get
            {
                return "Vibrato v1.0";
            }
        }

        #endregion

        #region constructor
        // constructor and initialization of controls

        /// <summary>
        ///  Creates new instance of Vibrato.
        /// </summary>
        /// <remarks>
        ///  Creates new instance with initial settings. It is necessary to set sample rate to work properly.
        /// </remarks>
        public Vibrato()
        {
            //buffer = new TimeVaryingBuffer(5, new TriangleOscilator());
            buffer = new OscillatingDelayLine(vibratoDelayTime, 44100, new TriangleOscilator(), 0);

            this.BackgroundImage = System.Drawing.Image.FromStream(new System.IO.MemoryStream(global::Effects.Recources.EffectBackgorunds.Vibrato));
            this.Size = BackgroundImage.Size;

            InitializeComponents();
            InitializeFootswitch();

            this.SetStyle(System.Windows.Forms.ControlStyles.AllPaintingInWmPaint |
                          System.Windows.Forms.ControlStyles.UserPaint |
                          System.Windows.Forms.ControlStyles.DoubleBuffer, true);
        }

        /// <summary>
        ///  Initializes all pots and oscillator switch.
        /// </summary>
        private void InitializeComponents()
        {
            Speed = new Pot();
            Speed.MaxValue = 3000;
            Speed.MinValue = 1;
            Speed.ValueChanged += new PotValueChangedEventHandler(this.SpeedPotChanged);
            Speed.Location = new System.Drawing.Point(20, 43);
            Speed.Knob = PotKnob.ChickenKnobMiddle;
            //Speed.Size = new System.Drawing.Size(110, 110);

            Depth = new Pot();
            Depth.MaxValue = 0.5;
            Depth.MinValue = 0;
            Depth.ValueChanged += new PotValueChangedEventHandler(this.DepthPotChanged);
            Depth.Location = new System.Drawing.Point(110, 43);
            Depth.Knob = PotKnob.ChickenKnobMiddle;
            //Depth.Size = new System.Drawing.Size(110, 110);

            oscilatorSwitch = new Switch(3);
            oscilatorSwitch.Location = new System.Drawing.Point(30, 120);
            oscilatorSwitch.Height = 30;
            oscilatorSwitch.Width = 20;
            oscilatorSwitch.OnSwitch += new SwitchSwitched(this.OscilatorSwitchSwitched);
            oscilatorSwitch.SwitchKnobColor = System.Drawing.Color.FromArgb(0, 0, 0);

            this.Controls.Add(Speed);
            this.Controls.Add(Depth);
            this.Controls.Add(oscilatorSwitch);
            Speed.Reset();
            Depth.Reset();
        }

        /// <summary>
        ///  Iniializes footswitch and led.
        /// </summary>
        private void InitializeFootswitch()
        {
            // footswitch
            footswitch = new Footswitch();
            switchedOn = true;
            footswitch.On = true;
            footswitch.Switched += new FootswitchSwitchedEventHandler(this.FootswitchSwitched);
            footswitch.Location = new System.Drawing.Point(125, 130);
            Controls.Add(footswitch);

            // led
            led = new LED();
            led.On = true;
            led.Location = new System.Drawing.Point(90, 30);
            Controls.Add(led);

            // synchronization
            synchronizer = new FootswitchLedSynchronizer(footswitch, led);
        }

        #endregion

        #region main_work
        // processing signal method

        // Overriding of effect method
        /// <summary>
        ///  Key method of Flanger. Modifies input signal
        /// </summary>
        /// <param name="data"> Input signal </param>
        /// <returns> Modified input signal </returns>
        public override double[] effect(double[] data)
        {
            if (switchedOn)
            {
                // process given data
                double[] outData = new double[data.Length];
                for (int i = 0; i < data.Length; i++)
                {
                    // store sample, get delayed and mix it with the one actually stored
                    outData[i] = (1 - depth) * data[i] + depth * buffer.Step(data[i]);

                }

                return outData;
            }
            else
            {
                // just pass data to the delay line and return dry input
                for (int i = 0; i < data.Length; i++)
                    buffer.Step(data[i]);

                return data;
            }
        }

        #endregion

        #region cotrols_event_handler
        // handling events from controls

        /// <summary>
        ///  Speed pot event handler. Adjusts period of oscillation.
        /// </summary>
        public void SpeedPotChanged(object sender, PotValueChangedEventArgs e)
        {
            this.buffer.Oscillator.Period = e.NewValueInt;
            this.speed = e.NewValueInt;

            EffectSettingsChangedEventArgs settings = new EffectSettingsChangedEventArgs(speedKey, speed);
            if (this.SettingsChanged != null)
                SettingsChanged(this, settings);
        }

        /// <summary>
        ///  Depth pot event handler. Adjust dry/wet mix
        /// </summary>
        public void DepthPotChanged(object sender, PotValueChangedEventArgs e)
        {
            this.depth = e.NewValue;

            EffectSettingsChangedEventArgs settings = new EffectSettingsChangedEventArgs(depthKey, depth);
            if (this.SettingsChanged != null)
                SettingsChanged(this, settings);
        }

        /// <summary>
        ///  Method sets oscillator according to the given value.
        /// </summary>
        /// <param name="state"> Type of the oscillator </param>
        private void SetOscillator(int state)
        {
            Oscillator newOscilator = null;
            switch (state)
            {
                case 0:
                    // triangle oscilator
                    newOscilator = new TriangleOscilator();
                    break;
                case 1:
                    //sin oscilator
                    newOscilator = new SinOscilator();
                    break;
                case 2:
                    newOscilator = new SquareOscilator();
                    break;
            }
            newOscilator.Period = this.buffer.Oscillator.Period;

            // set oscillator to the buffer
            this.buffer.Oscillator = newOscilator;
            this.oscillationType = state;
        }

        /// <summary>
        ///  OscillationSwitch event handler
        /// </summary>
        /// <param name="sender"> Switch that raised the event </param>
        /// <param name="e"> Event arguments </param>
        public void OscilatorSwitchSwitched(object sender, SwitchEventArgs e)
        {
            SetOscillator(e.State);

            EffectSettingsChangedEventArgs settings = new EffectSettingsChangedEventArgs(oscilatorTypeKey, e.State);
            if (this.SettingsChanged != null)
                SettingsChanged(this, settings);
        }

        /// <summary>
        ///  Footswitch.Switched event handler. According to the event arguments signal bypasses or runs through the effect.
        /// </summary>
        /// <param name="sender"> Object that has raised the event. </param>
        /// <param name="args"> Event Arguments </param>
        private void FootswitchSwitched(object sender, FootswitchSwitchedEventArgs args)
        {
            this.switchedOn = args.On;

            // raise the event
            if (this.SettingsChanged != null)
            {
                EffectSettingsChangedEventArgs changeArgs = new EffectSettingsChangedEventArgs(footswitchKey, switchedOn);
                this.SettingsChanged(this, changeArgs);
            }
        }

        #endregion

        #region settings
        // getting and setting the settings of Tremolo

        /// <summary>
        ///  Raised when any parameter of effect is changed.
        /// </summary>
        public override event EffectSettingsChangedDelegate SettingsChanged;

        /// <summary>
        ///  Gets all current settings of the effect.
        /// </summary>
        /// <returns> All current seetings </returns>
        public override object[] GetAllSettings()
        {
            object[] settings = new object[4];
            settings[0] = switchedOn;
            settings[1] = speed;
            settings[2] = depth;
            settings[3] = oscilatorSwitch.State;
            return settings;
        }

        /// <summary>
        ///  Sets all settings.
        /// </summary>
        /// <param name="settings"> All settings of the effect. This argument should be output of GetAllSettings method that has been called previously. </param>
        public override void SetAllSettings(object[] settings)
        {
            object[] settingsArray = (object[])settings;

            this.switchedOn = (bool)settingsArray[0];
            synchronizer.SetValue(switchedOn);
            

            this.speed = (int)settingsArray[1];
            buffer.Oscillator.Period = speed;
            Speed.SetValue(speed);

            this.depth = (double)settingsArray[2];
            Depth.SetValue(depth);

            SetOscillator((int)settingsArray[3]);
            oscilatorSwitch.State = oscillationType;
        }

        /// <summary>
        ///  Sets one settings
        /// </summary>
        /// <param name="e"> Setting change to be done </param>
        public override void SetSetting(EffectSettingsChangedEventArgs e)
        {
            int key = (int)e.Key;

            if (key == depthKey)
            {
                this.depth = (double)e.Value;
                Depth.SetValue(depth);
                return;
            }

            if (key == speedKey)
            {
                this.speed = (int)e.Value;
                buffer.Oscillator.Period = speed;
                Speed.SetValue(speed);

                return;
            }

            if (key == oscilatorTypeKey)
            {
                SetOscillator((int)e.Value);
                oscilatorSwitch.State = oscillationType;
                return;
            }

            if (key == footswitchKey)
            {
                switchedOn = (bool)e.Value;
                synchronizer.SetValue(switchedOn);
                return;
            }
        }

        /// <summary>
        ///  Return array desribing all parameters.
        /// </summary>
        /// <returns> Array describing parameters </returns>
        public override EffectParam[] GetParameters()
        {
            EffectParam[] parameters = new EffectParam[4];

            ListItem[] footswitchState = new ListItem[2];
            footswitchState[0] = new ListItem("Turn on", true);
            footswitchState[1] = new ListItem("Turn off", false);
            parameters[0] = new EffectParam(footswitchKey, "ON/OFF", footswitchState);

            EffectParam speedParam = new EffectParam(speedKey, "Speed", 30, 1000);
            EffectParam depthParam = new EffectParam(depthKey, "Depth", 0F, 1F);
            parameters[1] = speedParam;
            parameters[2] = depthParam;

            ListItem[] oscillationTypes = new ListItem[3];
            oscillationTypes[0] = new ListItem("Trianglewave oscillation", 0);
            oscillationTypes[1] = new ListItem("Sinuswave oscillation", 1);
            oscillationTypes[2] = new ListItem("Squarewave oscillation", 2);
            parameters[3] = new EffectParam(oscilatorTypeKey, "Oscillation type", oscillationTypes);

            return parameters;
        }

        #endregion
    }
}
